﻿// The MIT License (MIT)
// Copyright (c) 2014 LuoZhihui
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the “Software”), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;

namespace EasyTelnet
{
    //IAC OP COMMAND 

    /// <summary>
    /// Telnet协议报文解析器
    /// </summary>
    public class TelnetProtocolAnalyzer
    {
        private const int SEND_BUFFER_SIZE = 25;
        private readonly NetVirtualTerminal _netVirtualTerminal;

        public TelnetProtocolAnalyzer(NetVirtualTerminal netVirtualTerminal)
        {
            _netVirtualTerminal = netVirtualTerminal;
        }

        /// <summary>
        /// Telnet协议报文解析
        /// </summary>
        /// <param name="receivedStream"></param>
        /// <returns></returns>
        public DataAvailableEventArgments ProtocolAnalyze(DataStream receivedStream)
        {
            var dataAvailableEventArgs = new DataAvailableEventArgments();
            var respStream = new DataStream(receivedStream.WorkSocket, SEND_BUFFER_SIZE);
            while (receivedStream.Position < receivedStream.Length)
            {
                var aByte = receivedStream.ReadByte();
                //IAC NetVirtualTerminalCommand Option_Negotiated
                if (aByte == (byte) TelnetCommand.IAC)
                {
                    var commandByteValue= receivedStream.ReadByte();
                    var optionByteValue = receivedStream.ReadByte();

                    if (HandleNotSupportedOptionNegotiate(commandByteValue, optionByteValue, respStream))
                    {
                        continue;
                    }

                    HandleSupportedOptionNegotiate(commandByteValue, optionByteValue, receivedStream, respStream);
                }
                else
                {
                    dataAvailableEventArgs.WriteByte(aByte);
                }
            }

            return dataAvailableEventArgs;
        }

        #region 子选项协商

        /// <summary>
        /// 选项协商
        /// </summary>
        /// <param name="command"></param>
        /// <param name="optionByteValue"></param>
        /// <param name="respStream"></param>
        private static void OptionNegotiate(TelnetCommand command, byte optionByteValue, DataStream respStream)
        {
            // CLIENT		IAC	WILL	Terminal Type
            // SERVER		IAC	DO	Terminal Type
            respStream.ResetPosition();

            respStream.WriteByte(TelnetCommand.IAC);
            respStream.WriteByte(command);
            respStream.WriteByte(optionByteValue);

            respStream.SendBuffer();
        }

        /// <summary>
        /// 子选项协商
        /// </summary>
        /// <param name="option"></param>
        /// <param name="optionQualifier"></param>
        /// <param name="optionValueBytes"></param>
        /// <param name="respStream"></param>
        private static void SubOptionNegotiate(TelnetOption option, TelnetOptionQualifier optionQualifier, byte[] optionValueBytes, DataStream respStream)
        {
            //1=SEND , 0=IS
            // CLIENT		IAC	SB 	Terminal Type	1	IAC	SE
            // SERVER		IAC	SB 	Terminal Type	0	V	T	2	2	0	IAC	SE
            respStream.ResetPosition();
            respStream.WriteByte(TelnetCommand.IAC);
            respStream.WriteByte(TelnetCommand.SB);
            respStream.WriteByte(option);
            respStream.WriteByte(optionQualifier);

            if (optionQualifier == TelnetOptionQualifier.IS)
            {
                respStream.Write(optionValueBytes);
            }

            respStream.WriteByte(TelnetCommand.IAC);
            respStream.WriteByte(TelnetCommand.SE);
            respStream.SendBuffer();
        }

        #endregion


        #region 选项协商

        /// <summary>
        ///     处理不支持的协商选项
        /// </summary>
        /// <param name="commandByteValue"></param>
        /// <param name="optionByteValue"></param>
        /// <param name="respStream"></param>
        protected bool HandleNotSupportedOptionNegotiate(byte commandByteValue, byte optionByteValue, DataStream respStream)
        {
            var isHandled = false;
            switch (optionByteValue)
            {
                case (byte)TelnetOption.RemoteFlowControl:
                case (byte)TelnetOption.Echo:
                case (byte)TelnetOption.Status:
                case (byte)TelnetOption.SuppressGoAhead:
                case (byte)TelnetOption.OutputMarking:
                case (byte)TelnetOption.OutputLineWidth:
                case (byte)TelnetOption.OutputPageSize:
                case (byte)TelnetOption.OutputCarriageReturnDisposition:
                case (byte)TelnetOption.OutputHorizontalTabStops:
                case (byte)TelnetOption.OutputHorizontalTabDisposition:
                case (byte)TelnetOption.OutputFormfeedDisposition:
                case (byte)TelnetOption.OutputVerticalTabstops:
                case (byte)TelnetOption.OutputVerticalTabDisposition:
                case (byte)TelnetOption.OutputLinefeedDisposition:
                case (byte)TelnetOption.Reconnection:
                case (byte)TelnetOption.Authentication:
                case (byte)TelnetOption.NewEnvironment:
                case (byte)TelnetOption.ExtendedAscii:
                case (byte)TelnetOption.ByteMacro:
                case (byte)TelnetOption.DataEntryTerminal:
                case (byte)TelnetOption.Supdup:
                case (byte)TelnetOption.SupdupOutput:
                case (byte)TelnetOption.SendLocation:
                case (byte)TelnetOption.TacacsUserIdentification:
                case (byte)TelnetOption.TerminalLocationNumber:
                case (byte)TelnetOption.Telnet3270Regime:
                case (byte)TelnetOption.X3Pad:
                case (byte)TelnetOption.Charset:
                case (byte)TelnetOption.TerminalSpeed:
                case (byte)TelnetOption.XDisplayLocation:
                case (byte)TelnetOption.Environment:
                case (byte)TelnetOption.Encryption:
                case (byte)TelnetOption.Tn3270E:
                case (byte)TelnetOption.Xauth:
                case (byte)TelnetOption.ApproxMessageSize:
                case (byte)TelnetOption.TimingMark:
                case (byte)TelnetOption.RemoteControlledTransAndEcho:
                case (byte)TelnetOption.Linemode:
                case (byte)TelnetOption.TelnetRemoteSerialPortRsp:
                case (byte)TelnetOption.ComPortControl:
                case (byte)TelnetOption.TelnetSuppressLocalEcho:
                case (byte)TelnetOption.TelnetStartTls:
                case (byte)TelnetOption.Kermit:
                case (byte)TelnetOption.SendUrl:
                case (byte)TelnetOption.ForwardX:
                case (byte)TelnetOption.TeloptPragmaLogon:
                case (byte)TelnetOption.TeloptSspiLogon:
                case (byte)TelnetOption.TeloptPragmaHeartbeat:
                case (byte)TelnetOption.ExtendedOptionsList:
                    switch (commandByteValue)
                    {
                        case (byte)TelnetCommand.DO:
                        case (byte)TelnetCommand.DONT:
                            //RESP WONT
                            OptionNegotiate(TelnetCommand.WONT, optionByteValue, respStream);
                            break;
                        case (byte)TelnetCommand.WILL:
                        case (byte)TelnetCommand.WONT:
                            //RESP DONT
                            OptionNegotiate(TelnetCommand.DONT, optionByteValue, respStream);
                            break;
                        default: 
                            //满足语法规则要求，啥也不做
                            break;
                    }
                    isHandled = true;
                    break;
                default:
                    //满足语法规则要求，啥也不做
                    break;
            }

            return isHandled;
        }


        /// <summary>
        ///     处理支持的协商选项
        /// </summary>
        /// <param name="commandByteValue"></param>
        /// <param name="optionByteValue"></param>
        /// <param name="receivedStream"></param>
        /// <param name="respStream"></param>
        /// <returns></returns>
        private bool HandleSupportedOptionNegotiate(byte commandByteValue, byte optionByteValue, DataStream receivedStream, DataStream respStream)
        {
            var isHandled = true;
            switch (optionByteValue)
            {
                case (byte)TelnetOption.TerminalType:
                case (byte)TelnetOption.WindowSize:
                case (byte)TelnetOption.BinaryTransmission:
                case (byte)TelnetOption.Logout:
                case (byte)TelnetOption.EndOfRecord:
                    switch (commandByteValue)
                    {
                        case (byte)TelnetCommand.DO://253
                            //RESP WILL
                            OptionNegotiate(TelnetCommand.WILL, optionByteValue, respStream);
                            if (optionByteValue == (byte)TelnetOption.WindowSize)
                            {
                                SubOptionNegotiate(TelnetOption.WindowSize, TelnetOptionQualifier.IS, _netVirtualTerminal.TerminalSize.ToBytes, respStream);
                           }
                           
                            break;

                        case (byte)TelnetCommand.WILL://251
                            //RESP DO
                            OptionNegotiate(TelnetCommand.DO, optionByteValue, respStream);
                            break;

                        case (byte)TelnetCommand.DONT://254
                            //RESP WONT
                            OptionNegotiate(TelnetCommand.WONT, optionByteValue, respStream);
                            break;

                        case (byte)TelnetCommand.WONT://252
                            //RESP DONT
                            OptionNegotiate(TelnetCommand.DONT, optionByteValue, respStream);
                            break;
                        case (byte)TelnetCommand.SB://250
                            // SERVER		IAC	DO	Terminal Type
                            // CLIENT		IAC	WILL	Terminal Type
                            
                            //1=SEND , 0=IS
                            // SERVER		IAC	SB 	Terminal Type	SEND    IAC	SE
                            // CLIENT		IAC	SB 	Terminal Type	IS       	TTY	IAC	SE

                             TraversalToSePosition(receivedStream);
                            //协商终端类型
                            if (optionByteValue == TelnetOption.TerminalType.ByteValue())
                            {
                                SubOptionNegotiate(TelnetOption.TerminalType, TelnetOptionQualifier.IS, _netVirtualTerminal.TerminalType.ToBytes, respStream);
                            }
                            break;
                        default:
                            isHandled = false;
                            break;
                    }

                    break;
                default:
                    //满足语法规则要求，啥也不做
                    break;
            }

            return isHandled;
        }



        /// <summary>
        /// 遍历到 SE 代码的位置
        /// </summary>
        /// <param name="receivedStream"></param>
        private static void TraversalToSePosition(DataStream receivedStream)
        {
            while (receivedStream.Position < receivedStream.Length)
            {
                if (receivedStream.ReadByte() == TelnetCommand.SE.ByteValue())
                {
                    return;
                }
            }
        }

        #endregion
    }

    /// <summary>
    ///     Telnet 协议解析异常
    /// </summary>
    [Serializable]
    public class TelnetProtocolAnalyzeException : ApplicationException
    {
        public TelnetProtocolAnalyzeException(string msg)
            : base(msg)
        {
        }

        public TelnetProtocolAnalyzeException(string msg, Exception innerException)
            : base(msg, innerException)
        {
        }
    }
}